package com.cq.hd.member.factory;

import com.cq.hd.common.enums.biz.*;
import com.cq.hd.common.exception.BusinessException;
import com.cq.hd.common.response.Throw;
import com.cq.hd.common.utils.GenerateUtil;
import com.cq.hd.common.utils.LocalDateUtil;
import com.cq.hd.common.utils.NoUtils;
import com.cq.hd.common.utils.StringUtils;
import com.cq.hd.member.api.dto.WithdrawalApplyV2Dto;
import com.cq.hd.member.api.vo.BusinessEnterApplyDetailVo;
import com.cq.hd.member.api.vo.MerchantBankCardVo;
import com.cq.hd.member.api.vo.MerchantDetailVo;
import com.cq.hd.member.api.vo.MerchantEnterApplyVo;
import com.cq.hd.member.po.TbMerchantAmtBillPo;
import com.cq.hd.member.po.TbWithdrawalBillPo;
import com.cq.hd.member.service.*;
import com.cq.hd.order.api.TbMonthSettleBillApi;
import com.cq.hd.order.api.dto.MonthSettleBillWithdrawalDto;
import com.cq.hd.order.api.vo.MonthSettleBillVo;
import com.cq.hd.system.api.vo.WithdrawalConfigVo;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 基于月度账单的提现基类
 *
 * @author much
 * @since 2024/04/16 23:11
 */
@Slf4j
public abstract class BaseOnMonthSettleWithdrawalHandler extends AbstractWithdrawalHandler {

    @Resource
    private TbMerchantService merchantService;
    @Resource
    private TbMerchantEnterApplyService merchantEnterApplyService;
    @Resource
    private TbBusinessEnterApplyService businessEnterApplyService;
    @Resource
    private TbMerchantBankCardService merchantBankCardService;
    @Resource
    private TbWithdrawalBillService withdrawalBillService;
    @Resource
    private TbMerchantAmtBillService merchantAmtBillService;
    @Resource
    private PlatformTransactionManager transactionManager;
    @Resource
    protected TbMonthSettleBillApi monthSettleBillApi;

    @Override
    public Boolean applyV2(WithdrawalApplyV2Dto withdrawalApplyV2Dto, WithdrawalConfigVo withdrawalConfigVo) {
        // 初始化提现上下文
        WithdrawalContext withdrawalContext = initializeWithdrawalContext(withdrawalApplyV2Dto);
        // 检验余额
        validateWithdrawalAmount(withdrawalContext);
        // 设置提现银行卡信息
        setWithdrawalBankCard(withdrawalContext);
        // 执行提现事务
        performWithdrawalTransaction(withdrawalContext);

        return true;
    }

    /**
     * 设置提现银行卡，汇付帐号等
     *
     * @param withdrawalContext 提现上下文参数
     */
    private void setWithdrawalBankCard(WithdrawalContext withdrawalContext) {
        MerchantDetailVo merchantDetailVo = withdrawalContext.getMerchantDetailVo();
        String openid = merchantDetailVo.getOpenid();
        if (StringUtils.isBlank(openid)) {
            log.error("申请商户补贴提现失败，未找到商户的openid，merchantId：{}", merchantDetailVo.getId());
            Throw.isBusinessException("商户数据错误");
        }

        withdrawalContext.setHfId(merchantDetailVo.getHfMerchantNo());
        withdrawalContext.setTokenNo(merchantDetailVo.getWithdrawalCardNo());

        // 查询银行卡信息
        // 如果tb_merchant_bank_card表不为空，则先取这里的银行卡信息
        MerchantBankCardVo merchantBankCardVo = merchantBankCardService.getBankCardByMerchantId(withdrawalContext.merchantDetailVo.getId());
        if (merchantBankCardVo != null) {
            withdrawalContext.setCardName(merchantBankCardVo.getCardName());
            withdrawalContext.setCardNo(merchantBankCardVo.getCardNo());
            withdrawalContext.setBankName(merchantBankCardVo.getBankName());
        } else {
            MerchantEnterApplyVo enterInfoByOpenid = merchantEnterApplyService.getSuccessEnterInfoByOpenid(openid);
            if (enterInfoByOpenid == null) {
                // 表示自有商户申请提现，查找商家入驻数据
                BusinessEnterApplyDetailVo businessEnterApplyDetailVo = businessEnterApplyService.getSuccessEnterInfoByOpenid(openid);
                if (businessEnterApplyDetailVo == null) {
                    log.error("申请商户补贴提现失败，未找到商家/商户入驻的数据，merchantId：{}", withdrawalContext.getMerchantDetailVo().getId());
                    Throw.isBusinessException("商户数据错误");
                }
                withdrawalContext.setHfId(businessEnterApplyDetailVo.getHfMerchantNo());
                withdrawalContext.setAcctId(businessEnterApplyDetailVo.getHfAcctId());
                withdrawalContext.setTokenNo(businessEnterApplyDetailVo.getWithdrawalCardNo());
                withdrawalContext.setCardName(businessEnterApplyDetailVo.getCardName());
                withdrawalContext.setCardNo(businessEnterApplyDetailVo.getCardNo());
            } else {
                String bankCode = enterInfoByOpenid.getBankCode();
                if (!StringUtils.isBlank(bankCode)) {
                    HfBankIdEnum hfBankIdEnum = HfBankIdEnum.typeOfValue(bankCode);
                    if (hfBankIdEnum != null) {
                        withdrawalContext.setBankName(hfBankIdEnum.getName());
                    }
                }
                withdrawalContext.setCardName(enterInfoByOpenid.getCardName());
                withdrawalContext.setCardNo(enterInfoByOpenid.getCardNo());
            }
        }

        // 如果是结算池商户，需要手动打款
        if (FinancialAccountTypeEnum.PLATFORM.getValue() == merchantDetailVo.getFinancialAccountType()) {
            withdrawalContext.setHfId("");
            withdrawalContext.setTokenNo("");
        }

        // 需要判断提现银行卡是否已经绑定
        if (StringUtils.isBlank(withdrawalContext.getCardName()) || StringUtils.isBlank(withdrawalContext.getCardNo())) {
            log.error("申请商户补贴提现失败，未找到商户的银行卡相关数据，merchantId：{}", withdrawalContext.getMerchantDetailVo().getId());
            Throw.isBusinessException("商户数据错误");
        }
    }

    // 补贴提现备注
    private String getSubsidyRemark(List<MonthSettleBillVo> monthSettleBillVos) {
        String settleDateList = monthSettleBillVos.stream()
                .map(billVo -> {
                    LocalDate date = LocalDate.parse(billVo.getSettleDate() + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
                    return date.getMonthValue() + "月";
                })
                .distinct()
                .collect(Collectors.joining("、"));

        String businessShortName = monthSettleBillVos.get(0).getBusinessShortName();
        return businessShortName + settleDateList + "补贴提现";
    }

    private List<TbWithdrawalBillPo> createWithdrawalBills(WithdrawalContext withdrawalContext) {
        List<TbWithdrawalBillPo> tbWithdrawalBillList = new ArrayList<>();
        final BigDecimal[] beforeTradeAmt = {withdrawalContext.getMaxWithdrawalAmt()};
        MerchantDetailVo merchantDetailVo = withdrawalContext.getMerchantDetailVo();

        Map<Long, List<MonthSettleBillVo>> groupedByBusinessId = withdrawalContext.getGroupedByBusinessIdMonthSettleBills();
        groupedByBusinessId.forEach((businessId, monthSettleBillVoList) -> {
            // 总金额
            BigDecimal totalTradeAmt = monthSettleBillVoList.stream()
                    .map(MonthSettleBillVo::getTradeAmt)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            LocalDateTime nowLocalDateTime = LocalDateUtil.getNowLocalDateTime();
            String yyyyMMdd = LocalDateUtil.toLocalDateToString(nowLocalDateTime.toLocalDate(), "yyyyMMdd");
            String reqSeqId = LocalDateUtil.toLocalDateTimeToString(nowLocalDateTime, "yyyyMMddHHmmss") + GenerateUtil.getRandomNum(5);

            TbWithdrawalBillPo tbWithdrawalBillPo = new TbWithdrawalBillPo();
            tbWithdrawalBillPo.setApplyNo(NoUtils.getWithdrawalBillNo());
            tbWithdrawalBillPo.setWithdrawalUserId(merchantDetailVo.getId());
            tbWithdrawalBillPo.setBusinessId(businessId);
            tbWithdrawalBillPo.setWithdrawalType(withdrawalContext.getWithdrawalType());
            tbWithdrawalBillPo.setCardName(withdrawalContext.getCardName());
            tbWithdrawalBillPo.setCardNo(withdrawalContext.getCardNo());
            tbWithdrawalBillPo.setBankName(withdrawalContext.getBankName());
            tbWithdrawalBillPo.setBeforeAmt(beforeTradeAmt[0]);
            tbWithdrawalBillPo.setFeeAmt(BigDecimal.valueOf(0));
            tbWithdrawalBillPo.setTradeAmt(totalTradeAmt);
            tbWithdrawalBillPo.setAfterAmt(beforeTradeAmt[0].subtract(totalTradeAmt));
            tbWithdrawalBillPo.setReceiveAmt(totalTradeAmt);
            tbWithdrawalBillPo.setApplyTime(nowLocalDateTime);
            tbWithdrawalBillPo.setReqDate(yyyyMMdd);
            tbWithdrawalBillPo.setReqSeqId(reqSeqId);
            tbWithdrawalBillPo.setState(WithdrawalStateEnum.WAIT_AUDIT.getValue());
            tbWithdrawalBillPo.setHfId(withdrawalContext.getHfId());
            tbWithdrawalBillPo.setAcctId(withdrawalContext.getAcctId());
            tbWithdrawalBillPo.setTokenNo(withdrawalContext.getTokenNo());
            // 如果是本金提现，备注写提现单号
            if (withdrawalContext.getWithdrawalType().equals(WithdrawalTypeEnum.MERCHANT_AMT.getValue())) {
                tbWithdrawalBillPo.setRemark("提现编号：" + tbWithdrawalBillPo.getApplyNo());
            } else {
                tbWithdrawalBillPo.setRemark(getSubsidyRemark(monthSettleBillVoList));
            }
            tbWithdrawalBillPo.setMerchantFinancialAccountType(merchantDetailVo.getFinancialAccountType());
            tbWithdrawalBillPo.setBusinessFinancialAccountType(monthSettleBillVoList.get(0).getBusinessFinancialAccountType());
            tbWithdrawalBillPo.setCreateTime(nowLocalDateTime);
            tbWithdrawalBillPo.setUpdateTime(nowLocalDateTime);

            tbWithdrawalBillList.add(tbWithdrawalBillPo);

            beforeTradeAmt[0] = beforeTradeAmt[0].subtract(totalTradeAmt);
        });
        return tbWithdrawalBillList;
    }

    private boolean addMerchantAmtBillPo(TbWithdrawalBillPo tbWithdrawalBillPo, Integer withdrawalType) {
        // 增加商户资金流水记录
        LocalDateTime nowLocalDateTime = LocalDateUtil.getNowLocalDateTime();
        TbMerchantAmtBillPo tbMerchantAmtBillPo = new TbMerchantAmtBillPo();
        tbMerchantAmtBillPo.setBillNo(NoUtils.getMerchantAmtBillNo());
        tbMerchantAmtBillPo.setMerchantId(tbWithdrawalBillPo.getWithdrawalUserId());
        tbMerchantAmtBillPo.setBillType(BillTypeEnum.EXPEND.getValue());
        if (Objects.equals(withdrawalType, WithdrawalTypeEnum.MERCHANT_AMT.getValue())) {
            tbMerchantAmtBillPo.setTradeType(MemberAmtBillTradeTypeEnum.WITHDRAWAL_AMT.getValue());
        } else if (Objects.equals(withdrawalType, WithdrawalTypeEnum.MERCHANT_SUBSIDY.getValue())) {
            tbMerchantAmtBillPo.setTradeType(MemberAmtBillTradeTypeEnum.WITHDRAWAL_SUBSIDY.getValue());
        } else {
            Throw.isBusinessException("提现类型有误 withdrawalType=" + withdrawalType);
        }
        tbMerchantAmtBillPo.setRelationTradeNo(tbWithdrawalBillPo.getApplyNo());
        tbMerchantAmtBillPo.setBeforeAmt(tbWithdrawalBillPo.getBeforeAmt());
        tbMerchantAmtBillPo.setTradeAmt(tbWithdrawalBillPo.getTradeAmt());
        tbMerchantAmtBillPo.setAfterAmt(tbWithdrawalBillPo.getAfterAmt());
        tbMerchantAmtBillPo.setRemark(tbWithdrawalBillPo.getRemark());
        tbMerchantAmtBillPo.setTradeTime(tbWithdrawalBillPo.getApplyTime());
        tbMerchantAmtBillPo.setCreateTime(nowLocalDateTime);
        tbMerchantAmtBillPo.setUpdateTime(nowLocalDateTime);

        return merchantAmtBillService.save(tbMerchantAmtBillPo);
    }

    private void performWithdrawalTransaction(WithdrawalContext withdrawalContext) {
        List<TbWithdrawalBillPo> tbWithdrawalBillPos = createWithdrawalBills(withdrawalContext);
        Map<Long, List<MonthSettleBillVo>> groupedByBusinessId = withdrawalContext.getGroupedByBusinessIdMonthSettleBills();

        TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionAttribute());
        try {
            // 扣除商户补贴账户资金
            int subMerchantSubsidyAmtCount = updateMerchantAccountAmt(withdrawalContext);
            if (subMerchantSubsidyAmtCount == 0) {
                Throw.isBusinessException("商户可提现余额不足");
            }

            List<MonthSettleBillWithdrawalDto> monthSettleBillWithdrawalDtos = new ArrayList<>();
            for (TbWithdrawalBillPo tbWithdrawalBillPo : tbWithdrawalBillPos) {
                // 插入提现记录
                boolean save = withdrawalBillService.save(tbWithdrawalBillPo);
                // 在这里可以根据 save 的结果进行相应的处理
                if (!save) {
                    Throw.isBusinessException("申请提现失败");
                }

                // 商户资金流水记录
                save = addMerchantAmtBillPo(tbWithdrawalBillPo, withdrawalContext.getWithdrawalType());
                if (!save) {
                    Throw.isBusinessException("申请提现失败!");
                }

                MonthSettleBillWithdrawalDto withdrawalDto = new MonthSettleBillWithdrawalDto();
                withdrawalDto.setWithdrawalId(tbWithdrawalBillPo.getId());
                withdrawalDto.setMerchantId(tbWithdrawalBillPo.getWithdrawalUserId());
                List<MonthSettleBillVo> monthSettleBillVoList = groupedByBusinessId.get(tbWithdrawalBillPo.getBusinessId());
                List<Long> monthSettleMonthIds = monthSettleBillVoList.stream()
                        .map(MonthSettleBillVo::getId)
                        .collect(Collectors.toList());
                withdrawalDto.setMonthSettleBillIds(monthSettleMonthIds);
                monthSettleBillWithdrawalDtos.add(withdrawalDto);
            }

            // 更新月度结算单id状态，由 待提现 改成 提现中
            monthSettleBillApi.withdrawalByDto(monthSettleBillWithdrawalDtos).isSuccess();

            transactionManager.commit(transaction);
        } catch (Exception e) {
            //回滚事务
            transactionManager.rollback(transaction);
            log.error("<申请商户补贴提现V2>事务异常回滚", e);
            Throw.isBusinessException(e.getMessage());
        }
    }

    private WithdrawalContext initializeWithdrawalContext(WithdrawalApplyV2Dto withdrawalApplyV2Dto) {
        checkApplyParam(withdrawalApplyV2Dto);
        WithdrawalContext withdrawalContext = new WithdrawalContext();
        withdrawalContext.setWithdrawalType(getWithdrawalType());
        List<MonthSettleBillVo> monthSettleBillVos = getAvailableMonthSettleBills(withdrawalApplyV2Dto.getUserId(), withdrawalApplyV2Dto.getMonthSettleBillIds());
        withdrawalContext.setMonthSettleBills(monthSettleBillVos);

        Map<Long, List<MonthSettleBillVo>> groupedByBusinessId = new HashMap<>();
        // 如果是补贴，需要按照商家来分组
        if (withdrawalContext.getWithdrawalType().equals(WithdrawalTypeEnum.MERCHANT_SUBSIDY.getValue())) {
            groupedByBusinessId = withdrawalContext.getMonthSettleBills().stream()
                    .collect(Collectors.groupingBy(MonthSettleBillVo::getBusinessId));
        } else {
            groupedByBusinessId.put(0L, withdrawalContext.getMonthSettleBills());
        }
        withdrawalContext.setGroupedByBusinessIdMonthSettleBills(groupedByBusinessId);

        // 前端入参
        withdrawalContext.setWithdrawalApplyV2Dto(withdrawalApplyV2Dto);
        // 商户信息
        MerchantDetailVo merchantDetail = getMerchantDetail(withdrawalApplyV2Dto.getUserId());
        withdrawalContext.setMerchantDetailVo(merchantDetail);
        // 商户最大可提金额
        withdrawalContext.setMaxWithdrawalAmt(getMaxWithdrawalAmt(merchantDetail));
        // 提现金额
        withdrawalContext.setWithdrawalAmt(getWithdrawalAmount(withdrawalContext));
        return withdrawalContext;
    }

    private void checkApplyParam(WithdrawalApplyV2Dto withdrawalApplyV2Dto) {
        List<Long> monthSettleBillIds = withdrawalApplyV2Dto.getMonthSettleBillIds();
        if (CollectionUtils.isEmpty(monthSettleBillIds)) {
            Throw.isBusinessException("请先选择需要提现的结算账单");
        }
    }

    private MerchantDetailVo getMerchantDetail(Long merchantId) {
        return Optional.ofNullable(merchantService.getDetailById(merchantId))
                .orElseThrow(() -> new BusinessException("请先成为商户"));
    }

    private List<MonthSettleBillVo> getAvailableMonthSettleBills(Long merchantId, List<Long> monthSettleBillIds) {
        return monthSettleBillApi.getAvailableMonthSettleBillByIds(merchantId, monthSettleBillIds).unpack();
    }

    /**
     * 获取商户最大可提现金额
     *
     * @param merchantDetailVo 商户信息
     * @return 最大可提金额
     */
    protected abstract BigDecimal getMaxWithdrawalAmt(MerchantDetailVo merchantDetailVo);

    protected abstract BigDecimal getWithdrawalAmount(WithdrawalContext withdrawalContext);

    // 提现类型
    protected abstract Integer getWithdrawalType();

    protected abstract int updateMerchantAccountAmt(WithdrawalContext withdrawalContext);

    protected void validateWithdrawalAmount(WithdrawalContext withdrawalContext) {
        if (withdrawalContext.getWithdrawalAmt().compareTo(withdrawalContext.getMaxWithdrawalAmt()) > 0) {
            Throw.isBusinessException("商户可提现余额不足");
        }
    }

    @Data
    protected static class WithdrawalContext {
        // 提现类型
        Integer withdrawalType;
        // 前端入参
        WithdrawalApplyV2Dto withdrawalApplyV2Dto;
        // 用户信息
        MerchantDetailVo merchantDetailVo;
        // 月度结算单信息
        List<MonthSettleBillVo> monthSettleBills;
        // 按商家分组的月度结算单信息
        Map<Long, List<MonthSettleBillVo>> groupedByBusinessIdMonthSettleBills;
        // 最大可提金额
        private BigDecimal maxWithdrawalAmt;
        // 提现金额
        BigDecimal withdrawalAmt;

        // 提现银行卡账户相关
        String hfId;
        String acctId;
        String tokenNo;
        String cardName;
        String cardNo;
        String bankName;
    }
}
